package me.softwareengineer.guice.rendering.parser.imp;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;

import me.softwareengineer.guice.rendering.domain.AnchorElement;
import me.softwareengineer.guice.rendering.domain.BulletedListElement;
import me.softwareengineer.guice.rendering.domain.DOM;
import me.softwareengineer.guice.rendering.domain.DefinitionDescriptionElement;
import me.softwareengineer.guice.rendering.domain.DefinitionListElement;
import me.softwareengineer.guice.rendering.domain.DefinitionTermElement;
import me.softwareengineer.guice.rendering.domain.Element;
import me.softwareengineer.guice.rendering.domain.EmptyLineElement;
import me.softwareengineer.guice.rendering.domain.ListType;
import me.softwareengineer.guice.rendering.domain.NumberedListElement;
import me.softwareengineer.guice.rendering.domain.VirtualElement;
import me.softwareengineer.guice.rendering.listener.EndDefinitionDescriptionEvent;
import me.softwareengineer.guice.rendering.listener.EndDefinitionTermEvent;
import me.softwareengineer.guice.rendering.listener.EndElementEvent;
import me.softwareengineer.guice.rendering.listener.EndListEvent;
import me.softwareengineer.guice.rendering.listener.Event;
import me.softwareengineer.guice.rendering.listener.Listener;
import me.softwareengineer.guice.rendering.listener.OnAnchorEvent;
import me.softwareengineer.guice.rendering.listener.OnEmptyLineEvent;
import me.softwareengineer.guice.rendering.listener.StartElementEvent;
import me.softwareengineer.guice.rendering.parser.DomGeneratorListener;


/**
 * A helper class generates {@link DOM} tree based on the passed events.
 * 
 * <pre>
 * 	This class is NOT thread-safe and CANNOT be shared between two concurrent threads. Each time 
 * the user requires an instance of this class, Google Guice should create a new one. Providing
 * a singleton instance causes inconsistency in the generated {@link DOM} tree.
 * </pre>
 * @author yad.tahir
 * @see Listener
 * @see Event
 */
//Final mark is not required as this class is not immutable.
//TODO create test case for this vital class!
public class DomGeneratorListenerDefaultImp implements DomGeneratorListener{
	//Holds the generated elements temporarily. All elements will be pulled when an element
	//needs its children.
	protected Stack<Element> stack = new Stack<Element>();
	//Used to find the beginning of the HasChildren elements
	protected VirtualElement marker = new VirtualElement();

	
	/**
	 * {@inheritDoc}
	 * @see Listener
	 * @see Event
	 */
	@Override
	public void onEvent(Event event) {
		//Check the event type
		
			//All start events should add the begin marker.
			 if(event instanceof StartElementEvent){
				this.stack.push(marker);
				return;
			}
		
			//For anchor event 
			else if(event instanceof OnAnchorEvent){
				onAnchor((OnAnchorEvent) event);
				return;
			}
			//On List
			else if(event instanceof EndListEvent){
				//Check the list type
				EndListEvent e = (EndListEvent) event;
				if(e.getListType() == ListType.BULLETED){
					//Call End Bulleted List
					onEndBulletedList(e);
					return;
				}
				if(e.getListType() == ListType.NUMBERED){
					//Call End Numbered List
					onEndNumberedList(e);
					return;
				}
				if(e.getListType() == ListType.DEFINITION){
					//On Definition List
					onEndDefinitionList(e);
					return;
				}
			}
			
			//On Definition Description
			else if(event instanceof EndDefinitionDescriptionEvent){
				onEndDefinitionDescription((EndDefinitionDescriptionEvent) event);
			}
			
			 //On Empty Line event
			else if(event instanceof OnEmptyLineEvent){
				onEmptyLine((OnEmptyLineEvent) event);
			}
			 //On Definition Term 
			else if(event instanceof EndDefinitionTermEvent){
				onEndDefinitionTerm((EndDefinitionTermEvent) event);
			}
			
			
	}
	
	/**
	 * Called to notify about the end of {@link DefinitionTermElement}.
	 * 
	 * 
	 * @param event the end of defintion term event
	 * @see EndDefinitionTermEvent
	 * @see DefinitionTermElement
	 */
	protected void onEndDefinitionTerm(EndDefinitionTermEvent event){
		//Get the children from the stack 
		List<Element> children = generateListFromStack();
		//Construct the element 
		DefinitionTermElement element = new DefinitionTermElement(children);
		//Push it to the stack 
		this.stack.push(element);
	}
	
	/**
	 * Called to notify about an empty line element.
	 * @param event the on empty line event.
	 * 
	 * @see EmptyLineElement
	 * @see OnEmptyLineEvent
	 */
	protected void onEmptyLine(OnEmptyLineEvent event){
		this.stack.push(new EmptyLineElement(event.getEmptyLineCount()));
	}
	
	/**
	 * Called to notify about the end if {@link DefinitionListElement}
	 * 
	 * @param e the end definition list event 
	 * @see EndDefinitionListEvent
	 * @see #onEvent(Event)
	 * @see DefinitionListElement
	 * @see EndDefinitionListEvent
	 */
	protected void onEndDefinitionList(EndListEvent e){
		//Get the children from the stack 
		List<Element> chidlren = generateListFromStack();
		this.stack.push(new DefinitionListElement(chidlren,e.getProperties()));
	}
	
	/**
	 * Called to notify about the ending of {@link DefinitionDescriptionElement}
	 * 
	 * @param event the end definition description element
	 * @see EndDefinitionDescriptionEvent
	 * @see #onEvent(Event)
	 * @see DefinitionDescriptionElement
	 * @see EndElementEvent
	 */
	protected void onEndDefinitionDescription(EndDefinitionDescriptionEvent event){
		//Get the children
		List<Element> children = generateListFromStack();
		this.stack.push(new DefinitionDescriptionElement(children));
	}
	
	/**
	 * Called to notify about the ending of {@link BulletedListElement}
	 * @param event the end list event
	 * @see #onEvent(Event)
	 * @see EndListEvent
	 * @see BulletedListElement
	 */
	protected void onEndBulletedList(EndListEvent event){
		//Add a new bulleted list element to the stack list
		List<Element> children = generateListFromStack(); 
		this.stack.push(new BulletedListElement(children, event.getProperties()));
	}
	
	/**
	 * Called to notify about the ending of {@link Nu}
	 * @param event the end list event
	 * @see #onEvent(Event)
	 * @see EndListEvent
	 * @see NumberedListElement
	 */
	protected void onEndNumberedList(EndListEvent event){
		//Add a new numbered list element to the stack list
		List<Element> children = generateListFromStack(); 
		this.stack.push(new NumberedListElement(children, event.getProperties()));
	}
	
	/**
	 * Called when {@link OnAnchorEvent} arrived
	 * @param event the anchor event which is passed the listener
	 * @see OnAnchorEvent
	 * @see #onEvent(Event)
	 */
	protected void onAnchor(OnAnchorEvent event) {
		//Creates an anchor element and adds it to the stack
		AnchorElement e = new AnchorElement(event.getAnchorName());
		stack.add(e);
	}

	/**
	 * @return the DOM representation of the passed {@link Event}s
	 * @see DomGeneratorListenerDefaultImp#onEvent(Event)
	 */
	public DOM getDom(){
		List<Element> blocks = generateListFromStack();

		// support even events without begin/endDocument for partial content
		if (!blocks.isEmpty() && blocks.get(0) instanceof DOM) {
			return (DOM) blocks.get(0);
		} else {
			return new DOM(blocks);
		}
	}
	
	/**
	 * Generates a list of children passed on the pushed elements in the stack.
	 * @return list of elements 
	 */
	protected List<Element> generateListFromStack() {
		//Prepare the result
		List<Element> elements = new ArrayList<Element>();
		//Pop the elements of the stack
		while (!this.stack.empty()) {
			if (this.stack.peek() != this.marker) {
				elements.add(this.stack.pop());
			} else {
				//If it is the marker, just pop it from the stack
				this.stack.pop();
				break;
			}
		}
		Collections.reverse(elements);
		return elements;
	}
	

}
